
package slap.mvc;

import java.io.File;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

import org.apache.commons.javaflow.Continuation;
import org.apache.commons.javaflow.bytecode.StackRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import slap.app.Application;
import slap.concurrent.Suspend;
import slap.exception.TemplateNotFoundException;
import slap.exception.UnexpectedException;
import slap.http.Request;
import slap.mvc.result.BadRequest;
import slap.mvc.result.Error;
import slap.mvc.result.Forbidden;
import slap.mvc.result.NotFound;
import slap.mvc.result.NotModified;
import slap.mvc.result.Ok;
import slap.mvc.result.Redirect;
import slap.mvc.result.RedirectToStatic;
import slap.mvc.result.RenderBinary;
import slap.mvc.result.RenderHtml;
import slap.mvc.result.RenderStatic;
import slap.mvc.result.RenderTemplate;
import slap.mvc.result.RenderText;
import slap.mvc.result.Result;
import slap.mvc.result.Unauthorized;
import slap.mvc.scope.Flash;
import slap.mvc.scope.RenderModels;
import slap.mvc.scope.Session;
import slap.paradigm.Procedure;
import slap.paradigm.Procedure.Procedure1;
import slap.paradigm.ProcedureV;
import slap.template.Template;
import slap.util.Time;

public abstract class HttpRequestController extends Controller {
	protected static final Logger logger = LoggerFactory.getLogger(HttpRequestController.class);
	protected void renderText(Object text) {
		throw new RenderText(text == null ? "" : text.toString());
	}

	protected void renderHtml(Object html) {
		throw new RenderHtml(html == null ? "" : html.toString());
	}

	protected void renderText(CharSequence pattern, Object... args) {
		throw new RenderText(pattern == null ? "" : String.format(
				pattern.toString(), args));
	}

	protected void renderBinary(InputStream is) {
		throw new RenderBinary(is, null, true);
	}

	protected void renderBinary(InputStream is, long length) {
		throw new RenderBinary(is, null, length, true);
	}

	protected void renderBinary(InputStream is, String name) {
		throw new RenderBinary(is, name, false);
	}

	protected void renderBinary(InputStream is, String name, long length) {
		throw new RenderBinary(is, name, length, false);
	}

	protected void renderBinary(InputStream is, String name, boolean inline) {
		throw new RenderBinary(is, name, inline);
	}

	protected void renderBinary(InputStream is, String name, long length,
			boolean inline) {
		throw new RenderBinary(is, name, length, inline);
	}

	protected void renderBinary(InputStream is, String name,
			String contentType, boolean inline) {
		throw new RenderBinary(is, name, contentType, inline);
	}

	protected void renderBinary(InputStream is, String name, long length,
			String contentType, boolean inline) {
		throw new RenderBinary(is, name, length, contentType, inline);
	}

	protected void renderBinary(File file) {
		throw new RenderBinary(file);
	}

	protected void renderBinary(File file, String name) {
		throw new RenderBinary(file, name);
	}

	protected void notModified() {
		throw new NotModified();
	}

	protected void badRequest() {
		throw new BadRequest();
	}

	protected void unauthorized(String realm) {
		throw new Unauthorized(realm);
	}

	protected void unauthorized() {
		throw new Unauthorized("Unauthorized");
	}

	protected void notFound(String what) {
		throw new NotFound(what);
	}

	protected void ok() {
		throw new Ok();
	}

	protected void notFoundIfNull(Object o) {
		if (o == null) {
			notFound();
		}
	}

	protected void notFoundIfNull(Object o, String what) {
		if (o == null) {
			notFound(what);
		}
	}

	protected void notFound() {
		throw new NotFound("");
	}

	protected void checkAuthenticity() {
		// TODO
	}

	protected void forbidden(String reason) {
		throw new Forbidden(reason);
	}

	protected void forbidden() {
		throw new Forbidden("Access denied");
	}

	protected void error(int status, String reason) {
		throw new Error(status, reason);
	}

	protected void error(String reason) {
		throw new Error(reason);
	}

	protected void error(Exception reason) {
		logger.error("error()", reason);
		throw new Error(reason.toString());
	}

	protected void error() {
		throw new Error("Internal Error");
	}

	protected void flash(String key, Object value) {
		Flash.current().put(key, value);
	}

	protected void redirect(String url) {
		redirect(url, false);
	}

	protected void redirectToStatic(String file) {
	}

	protected void redirect(String url, boolean permanent) {
		if (url.matches("^([^./]+[.]?)+$")) { // fix Java !
			redirect(url, permanent, new Object[0]);
		}
		throw new Redirect(url, permanent);
	}

	public void redirect(String action, Object... args) {
		redirect(action, false, args);
	}

	protected boolean templateExists(String templateName) {
		return Template._load(templateName) != null;
	}

	protected void renderTemplate(String templateName) {
		Map<String, Object> templateBinding = new HashMap<String, Object>(16);
		renderTemplate(templateName, templateBinding);
	}

	protected void renderTemplate(String templateName, Map<String, Object> args) {
		RenderModels templateBinding = RenderModels.current();
		templateBinding.put("app_url", this.application.url);
		templateBinding.put("static_url", this.application.url+"/static");
		templateBinding.data.putAll(args);
		templateBinding.put("session", Session.current());
		templateBinding.put("request", Request.current());
		templateBinding.put("flash", Flash.current());
		try {
			throw new RenderTemplate(this.application.getAppName()
					+ templateName, templateBinding.data);
		} catch (TemplateNotFoundException ex) {
			// TODO
			ex.printStackTrace();
		}
	}

	protected void renderStatic(String staticName) {
		throw new RenderStatic(
				"/" + this.application.getAppName() + staticName, application);
	}

	protected static void await(String timeout) {
		await(1000 * Time.parseDuration(timeout));
	}

	protected static void await(String timeout, ProcedureV callback) {
		await(1000 * Time.parseDuration(timeout), callback);
	}

	protected static void await(int millis) {
		Request.current().isNew = false;
		Continuation.suspend(millis);
	}

	protected static void await(int millis, ProcedureV callback) {
		Request.current().isNew = false;
		Request.current().callback = callback;
		throw new Suspend(millis);
	}

	@SuppressWarnings("unchecked")
	protected static <T> T await(Future<T> future) {
		if (future != null) {
			Request.current().future = future;
		} else if (Request.current().future != null) {
			// Since the continuation will restart in this code that isn't
			// intstrumented by javaflow,
			// we need to reset the state manually.
			StackRecorder.get().isCapturing = false;
			StackRecorder.get().isRestoring = false;
			StackRecorder.get().value = null;
			future = (Future<T>) Request.current().future;
			// Now reset the Controller invocation context
			//ControllerInstrumentation.stopActionCall();
		} else {
			throw new UnexpectedException("Lost promise for "
					+ Request.current() + "!");
		}

		if (future.isDone()) {
			try {
				return future.get();
			} catch (Exception e) {
				throw new UnexpectedException(e);
			}
		} else {
			Request.current().isNew = false;
			Continuation.suspend(future);
			return null;
		}
	}

	protected static <T> void await(Future<T> future, Procedure1<T> callback) {
		Request.current().isNew = false;
		Request.current().future = future;
		Request.current().callback = callback;
		throw new Suspend(future);
	}
}
